package com.zhouyer.study.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * IP防暴刷（限制单个客户端IP在最近X分钟内请求注册接⼝不能超过Y次）
 *
 * @author zhouyao
 * @version 1.0
 * @date 2021/12/7 10:02 上午
 */
@Component
@Slf4j
public class IpLimitFilter implements GlobalFilter, Ordered {
    @Value("${filter.limit.ip.maxtimes}")
    private int maxtimes;

    @Value("${filter.limit.ip.maxlimitminutes}")
    private int maxlimitminutes;

    @Value("${filter.limit.ip.url}")
    private String ipLimitUrl;

    public IpLimitFilter() {
        System.out.println("IpLimitFilter创建成功");
    }

    // 用于保存每个客户端ip请求的时间，key为客户端ip，value为该客户端访问限定请求时的时间
    private static Map<String, CopyOnWriteArrayList<Long>> ipLimitCache = new ConcurrentHashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 访问路径
        String path = request.getPath().toString();
        // 客户端ip
        String clientIp = request.getRemoteAddress().getHostString();
        // 对设置限制的url进行拦截处理
        if (path.startsWith(ipLimitUrl)) {
            CopyOnWriteArrayList<Long> timeList = ipLimitCache.get(clientIp);
            // 如果当前ip在缓存中不存在，说明是第一次访问
            if (CollectionUtils.isEmpty(timeList)) {
                // 创建记录时间的集合，并存入缓存
                timeList = new CopyOnWriteArrayList<>();
                ipLimitCache.put(clientIp, timeList);
            }
            // 添加当前时间
            timeList.add(System.currentTimeMillis());
            // 遍历当前客户端ip中的所有访问时间，在指定时间范围内的，就进行统计
            int count = 0;
            Iterator<Long> iterator = timeList.iterator();
            // 计算指定时间范围的开始时间，如果时间在这个startTime之后的就是在该时间范围的的请求
            Long startTime = System.currentTimeMillis() - maxlimitminutes * 60000;
            while (iterator.hasNext()) {
                Long next = iterator.next();
                if (next >= startTime) {
                    count++;
                } else {
                    // 如果在指定时间范围以外的数据直接清除掉，防止List集合数据膨胀
                    timeList.remove(next);
                }
            }
            if (count > maxtimes) {
                // 执行访问，直接返回
                // 设置状态码为303
                response.setStatusCode(HttpStatus.SEE_OTHER);
                log.info("=======IP:{}暴刷", clientIp);
                String data = "您频繁进⾏注册，请求已被拒绝";
                DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
                return response.writeWith(Mono.just(wrap));
            }
        }
        // 合法请求，放行，执行后续的过滤器
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
